package org.hawk.gwt.ppc.reflect.emul.java.lang.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;

import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.GWTClassType;
import org.hawk.gwt.ppc.reflect.lang.GWTTypeVariable;

/**
 * Represents delegate for constructor to create class instances
 * represented by class delegate.
 * 
 * @author alex.bereznevatiy@gmail.com
 * 
 */
public final class Constructor<T> extends AccessibleObject implements GenericDeclaration, Member {
	private ClassDelegate<T> clazz;
	private String signature;
	private Object accessor;
	private boolean varAgrs;
	private GWTTypeVariable<Constructor<T>>[] typeParameters;
	
	public Constructor(Object accessor, ClassDelegate<T> clazz, String signature, boolean varAgrs) {
		checkAccess(accessor);
		this.accessor = accessor;
		this.clazz = clazz;
		this.signature = signature;
		this.typeParameters = clazz.getMethodTypeParameters(this, accessor, signature);
		this.varAgrs = varAgrs;
	}
	/**
	 * Returns the <code>Class</code> object representing the class that
	 * declares the constructor represented by this <code>Constructor</code>
	 * object.
	 */
	public Class<T> getDeclaringClass() {
		return clazz.toClass();
	}

	/**
	 * Returns the name of this constructor, as a string. This is always the
	 * same as the simple name of the constructor's declaring class.
	 */
	public String getName() {
		return clazz.getName();
	}

	/**
	 * Returns the Java language modifiers for the constructor represented by
	 * this <code>Constructor</code> object, as an integer. The
	 * <code>Modifier</code> class should be used to decode the modifiers.
	 * 
	 * @see Modifier
	 */
	public int getModifiers() {
		return clazz.getModifiers(accessor,signature);
	}

	/**
	 * Returns an array of <tt>TypeVariable</tt> objects that represent the type
	 * variables declared by the generic declaration represented by this
	 * <tt>GenericDeclaration</tt> object, in declaration order. Returns an
	 * array of length 0 if the underlying generic declaration declares no type
	 * variables.
	 * 
	 * @return an array of <tt>TypeVariable</tt> objects that represent the type
	 *         variables declared by this generic declaration
	 * @throws GenericSignatureFormatError
	 *             if the generic signature of this generic declaration does not
	 *             conform to the format specified in the Java Virtual Machine
	 *             Specification, 3rd edition
	 * @since 1.5
	 */
	@SuppressWarnings("unchecked")
	public TypeVariable<Constructor<T>>[] getTypeParameters() {
		if (this.typeParameters == null) {
			thereIsNoGenericSupport();
		}
		TypeVariable<Constructor<T>>[] result = (TypeVariable<Constructor<T>>[]) new TypeVariable<?>[this.typeParameters.length];
		for (int i = 0; i < this.typeParameters.length; i++) {
			result[i] = (TypeVariable<Constructor<T>>) this.typeParameters[i];
		}
		return result;
	}

	/**
	 * Returns an array of <code>Class</code> objects that represent the formal
	 * parameter types, in declaration order, of the constructor represented by
	 * this <code>Constructor</code> object. Returns an array of length 0 if the
	 * underlying constructor takes no parameters.
	 * 
	 * @return the parameter types for the constructor this object represents
	 */
	public Class<?>[] getParameterTypes() {
		return ClassDelegate.unmarshalSignature(signature);
	}

	/**
	 * Returns an array of <tt>Type</tt> objects that represent the formal
	 * parameter types, in declaration order, of the method represented by this
	 * <tt>Constructor</tt> object. Returns an array of length 0 if the
	 * underlying method takes no parameters.
	 * 
	 * <p>
	 * If a formal parameter type is a parameterized type, the <tt>Type</tt>
	 * object returned for it must accurately reflect the actual type parameters
	 * used in the source code.
	 * 
	 * <p>
	 * If a formal parameter type is a type variable or a parameterized type, it
	 * is created. Otherwise, it is resolved.
	 * 
	 * @return an array of <tt>Type</tt>s that represent the formal parameter
	 *         types of the underlying method, in declaration order
	 * @throws GenericSignatureFormatError
	 *             if the generic method signature does not conform to the
	 *             format specified in the Java Virtual Machine Specification,
	 *             3rd edition
	 * @throws TypeNotPresentException
	 *             if any of the parameter types of the underlying method refers
	 *             to a non-existent type declaration
	 * @throws MalformedParameterizedTypeException
	 *             if any of the underlying method's parameter types refer to a
	 *             parameterized type that cannot be instantiated for any reason
	 * @since 1.5
	 */
	public Type[] getGenericParameterTypes() {
		Type[] result = clazz.getMethodGenericParameterTypes(this, accessor, signature);
		if (result == null) {
			thereIsNoGenericSupport();
		}
		if (result.length != 0) {
			return result;
		}
		Class<?>[] params = getParameterTypes();
		result = new Type[params.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = ClassDelegate.forClass(params[i]);
		}
		return result;
	}

	/**
	 * Returns an array of <code>Class</code> objects that represent the types
	 * of exceptions declared to be thrown by the underlying constructor
	 * represented by this <code>Constructor</code> object. Returns an array of
	 * length 0 if the constructor declares no exceptions in its
	 * <code>throws</code> clause.
	 * 
	 * @return the exception types declared as being thrown by the constructor
	 *         this object represents
	 */
	public Class<?>[] getExceptionTypes() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns an array of <tt>Type</tt> objects that represent the exceptions
	 * declared to be thrown by this <tt>Constructor</tt> object. Returns an
	 * array of length 0 if the underlying method declares no exceptions in its
	 * <tt>throws</tt> clause.
	 * 
	 * <p>
	 * If an exception type is a parameterized type, the <tt>Type</tt> object
	 * returned for it must accurately reflect the actual type parameters used
	 * in the source code.
	 * 
	 * <p>
	 * If an exception type is a type variable or a parameterized type, it is
	 * created. Otherwise, it is resolved.
	 * 
	 * @return an array of Types that represent the exception types thrown by
	 *         the underlying method
	 * @throws GenericSignatureFormatError
	 *             if the generic method signature does not conform to the
	 *             format specified in the Java Virtual Machine Specification,
	 *             3rd edition
	 * @throws TypeNotPresentException
	 *             if the underlying method's <tt>throws</tt> clause refers to a
	 *             non-existent type declaration
	 * @throws MalformedParameterizedTypeException
	 *             if the underlying method's <tt>throws</tt> clause refers to a
	 *             parameterized type that cannot be instantiated for any reason
	 * @since 1.5
	 */
	public Type[] getGenericExceptionTypes() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Compares this <code>Constructor</code> against the specified object.
	 * Returns true if the objects are the same. Two <code>Constructor</code>
	 * objects are the same if they were declared by the same class and have the
	 * same formal parameter types.
	 */
	public boolean equals(Object obj) {
		if(obj==null)return false;
		if(this==obj)return true;
		if(obj instanceof Constructor){
			Constructor<?> c = (Constructor<?>)obj;
			if(c.signature.equals(signature)){
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns a hashcode for this <code>Constructor</code>. The hashcode is the
	 * same as the hashcode for the underlying constructor's declaring class
	 * name.
	 */
	public int hashCode() {
		return getDeclaringClass().getName().hashCode();
	}

	/**
	 * Returns a string describing this <code>Constructor</code>. The string is
	 * formatted as the constructor access modifiers, if any, followed by the
	 * fully-qualified name of the declaring class, followed by a parenthesized,
	 * comma-separated list of the constructor's formal parameter types. For
	 * example:
	 * 
	 * <pre>
	 *    public java.util.Hashtable(int,float)
	 * </pre>
	 * 
	 * <p>
	 * The only possible modifiers for constructors are the access modifiers
	 * <tt>public</tt>, <tt>protected</tt> or <tt>private</tt>. Only one of
	 * these may appear, or none if the constructor has default (package)
	 * access.
	 */
	public String toString() {
		String mod = Modifier.toString(getModifiers());
		if (!mod.isEmpty()) {
			mod += ' ';
		}
		String name = clazz.getName();
		int i = name.lastIndexOf('.');
		if (i < 0 || name.length() <= ++i) {
			return mod + signature;
		}
		return mod + name.substring(0, i) + signature;
	}

	/**
	 * Returns a string describing this <code>Constructor</code>, including type
	 * parameters. The string is formatted as the constructor access modifiers,
	 * if any, followed by an angle-bracketed comma separated list of the
	 * constructor's type parameters, if any, followed by the fully-qualified
	 * name of the declaring class, followed by a parenthesized, comma-separated
	 * list of the constructor's generic formal parameter types. A space is used
	 * to separate access modifiers from one another and from the type
	 * parameters or return type. If there are no type parameters, the type
	 * parameter list is elided; if the type parameter list is present, a space
	 * separates the list from the class name. If the constructor is declared to
	 * throw exceptions, the parameter list is followed by a space, followed by
	 * the word &quot;<tt>throws</tt>&quot; followed by a comma-separated list
	 * of the thrown exception types.
	 * 
	 * <p>
	 * The only possible modifiers for constructors are the access modifiers
	 * <tt>public</tt>, <tt>protected</tt> or <tt>private</tt>. Only one of
	 * these may appear, or none if the constructor has default (package)
	 * access.
	 * 
	 * @return a string describing this <code>Constructor</code>, include type
	 *         parameters
	 * 
	 * @since 1.5
	 */
	public String toGenericString() {
		StringBuilder result = new StringBuilder(Modifier.toString(getModifiers()));
		if (result.length() != 0) {
			result.append(' ');
		}
		TypeVariable<Constructor<T>>[] vars = getTypeParameters();
		if (vars.length > 0) {
			result.append('<');
			for (int i = 0; i < vars.length; i++) {
				if (i > 0) {
					result.append(',');
				}
				result.append(vars[i]);
			}
			result.append("> ");
		}
		result.append(getName());
		result.append('(');
		Type[] params = getGenericParameterTypes();
		for (int i = 0; i < params.length; i++) {
			if (i > 0) {
				result.append(',');
			}
			if (params[i] instanceof GWTClassType) {
				result.append(((GWTClassType<?>)params[i]).toClass().getName());
			} else {
				result.append(params[i]);
			}
		}
		result.append(')');
		return result.toString();
	}

	/**
	 * Uses the constructor represented by this <code>Constructor</code> object
	 * to create and initialize a new instance of the constructor's declaring
	 * class, with the specified initialization parameters. Individual
	 * parameters are automatically unwrapped to match primitive formal
	 * parameters, and both primitive and reference parameters are subject to
	 * method invocation conversions as necessary.
	 * 
	 * <p>
	 * If the number of formal parameters required by the underlying constructor
	 * is 0, the supplied <code>initargs</code> array may be of length 0 or
	 * null.
	 * 
	 * <p>
	 * If the constructor's declaring class is an inner class in a non-static
	 * context, the first argument to the constructor needs to be the enclosing
	 * instance; see <i>The Java Language Specification</i>, section 15.9.3.
	 * 
	 * <p>
	 * If the required access and argument checks succeed and the instantiation
	 * will proceed, the constructor's declaring class is initialized if it has
	 * not already been initialized.
	 * 
	 * <p>
	 * If the constructor completes normally, returns the newly created and
	 * initialized instance.
	 * 
	 * @param initargs
	 *            array of objects to be passed as arguments to the constructor
	 *            call; values of primitive types are wrapped in a wrapper
	 *            object of the appropriate type (e.g. a <tt>float</tt> in a
	 *            {@link java.lang.Float Float})
	 * 
	 * @return a new object created by calling the constructor this object
	 *         represents
	 * 
	 * @exception IllegalAccessException
	 *                if this <code>Constructor</code> object enforces Java
	 *                language access control and the underlying constructor is
	 *                inaccessible.
	 * @exception IllegalArgumentException
	 *                if the number of actual and formal parameters differ; if
	 *                an unwrapping conversion for primitive arguments fails; or
	 *                if, after possible unwrapping, a parameter value cannot be
	 *                converted to the corresponding formal parameter type by a
	 *                method invocation conversion; if this constructor pertains
	 *                to an enum type.
	 * @exception InstantiationException
	 *                if the class that declares the underlying constructor
	 *                represents an abstract class.
	 * @exception InvocationTargetException
	 *                if the underlying constructor throws an exception.
	 * @exception ExceptionInInitializerError
	 *                if the initialization provoked by this method fails.
	 */
	public T newInstance(Object... initargs) throws InstantiationException,
			IllegalAccessException, IllegalArgumentException,
			InvocationTargetException {
		checkAccess();
		if (clazz.isEnum()) {
			throw new IllegalArgumentException("Enum class cannot be instantiated through reflection: " + clazz.getName());
		}
		if (Modifier.isAbstract(clazz.getModifiers())) {
			throw new InstantiationException("Abstract class cannot be instantiated: " + clazz.getName());
		}
		Method.checkParameters(initargs,getParameterTypes());
		T instance = null;
		try {
			instance = clazz.create(accessor, signature, initargs);
		} catch (Throwable th) {
			throw new java.lang.reflect.InvocationTargetException(th);
		}
		if (instance == null) {
			throw new InstantiationException("Internal Error. This is probably a gwt preprocessor bug.");
		}
		return instance;
	}
	/**
	 * Returns <tt>true</tt> if this constructor was declared to take a variable
	 * number of arguments; returns <tt>false</tt> otherwise.
	 * 
	 * @return <tt>true</tt> if an only if this constructor was declared to take
	 *         a variable number of arguments.
	 * @since 1.5
	 */
	public boolean isVarArgs() {
		return this.varAgrs;
	}

	/**
	 * Returns <tt>true</tt> if this constructor is a synthetic constructor;
	 * returns <tt>false</tt> otherwise.
	 * 
	 * @return true if and only if this constructor is a synthetic constructor
	 *         as defined by the Java Language Specification.
	 * @since 1.5
	 */
	public boolean isSynthetic() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @throws NullPointerException
	 *             {@inheritDoc}
	 * @since 1.5
	 */
	@SuppressWarnings("hiding")
	public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @since 1.5
	 */
	public Annotation[] getDeclaredAnnotations() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns an array of arrays that represent the annotations on the formal
	 * parameters, in declaration order, of the method represented by this
	 * <tt>Constructor</tt> object. (Returns an array of length zero if the
	 * underlying method is parameterless. If the method has one or more
	 * parameters, a nested array of length zero is returned for each parameter
	 * with no annotations.) The annotation objects contained in the returned
	 * arrays are serializable. The caller of this method is free to modify the
	 * returned arrays; it will have no effect on the arrays returned to other
	 * callers.
	 * 
	 * @return an array of arrays that represent the annotations on the formal
	 *         parameters, in declaration order, of the method represented by
	 *         this Constructor object
	 * @since 1.5
	 */
	public Annotation[][] getParameterAnnotations() {
		throw new UnsupportedOperationException();
	}
}
